package kayao.server.network;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.UnknownHostException;

import kayao.common.KayaoDatagramSocket;
import kayao.common.KayaoValues;
import kayao.server.network.message.*;

public class NetwriteThread extends Thread {
	public static final Object LOCK = new Object();

	private KayaoDatagramSocket mSendSocket;
	private DatagramPacket packet;
	private boolean mRunning = true;
	private boolean mDead = false;
	private boolean mDebug = false;
	private int mLogoutCount = 0;

	public NetwriteThread(KayaoDatagramSocket socket){
		mSendSocket=socket;
		try{
			mSendSocket = new KayaoDatagramSocket(KayaoValues.CLIENT_SEND_PORT);
		} catch(Exception e){
			System.out.println("Error in NetwriteThread constructor: " + e.getMessage());
		}
	}
	
	public boolean terminate(){
		if (mDebug) { System.out.println("[NETWRITE] -  Trying to close netwrite"); }
		mRunning = false;
		mSendSocket.close();
		if (mSendSocket.isClosed() == false){
			System.out.println("[NETWRITE] -  (NetwriteThread.terminate) Unable to close socket");
		} else {
			if (mDebug) { System.out.println("[NETWRITE] -  Socket is now closed"); }
		}
		int i = 0;
		while(!mDead){
			synchronized(LOCK){
				this.interrupt();
				LOCK.notify();
			}
			if (i < 10){
				if (mDebug) { System.out.println("[NETWRITE] -  end of dead loop"); }
				i++;
			} else {
				break;
			}
		}
		return false;
	}
	@Override
	public void run() {
		if (mDebug) { System.out.println("### NetwriteThread started"); }
		while(mRunning) {
			if (!MessageHandler.gotMessagesInQueue()) {
				synchronized(LOCK) {
					try {
						LOCK.wait();
					} catch (InterruptedException e) {
						if (mDebug) { System.out.println("[NETWRITE] -  Interrupted from wait"); }
					}
				}
			} else {
				try {
					while (MessageHandler.gotMessagesInQueue()) {
						if (mDebug) { System.out.println("[NETWRITE] -  Got messages in que"); }
						sendOutgoingMessages();
						treatWaitingMessages();
					}					
				} catch (NullPointerException e){
					if (mDebug) { System.out.println("[NETWRITE] -  Nullpointer in run"); }
				}
			}
		}
		mDead = true;
	}

	/**
	 * Get the response messages and treat them.
	 * They can be put in the outgoing queue or ignored if they have tried enough
	 */
	private void treatWaitingMessages() {
		try {
			if (MessageHandler.gotMessagesAwaitingReply()) {
				// Message manager need to abort sleep if a new message to send is added
				Message m;
				while((m=MessageHandler.getNextMessageAwaitingReply())!=null) {
					if (mDebug) { System.out.println("[NETWRITE] -  while in treating messages"); }
					long time=System.currentTimeMillis();
					if(time>m.getTimeout())
					{
						System.out.println("[NETWRITE] -  Got a timeout at msg: " + m.getMessageId() + ", type: "+m.type() +" " + time + " " + m.getTimeout());
						if(m.getRetriesLeft()>=0) {
							MessageHandler.removeAwatingMessage(m.getMessageId());
							MessageHandler.insertOutgoingMessage(m);
						}
						else
						{
							MessageHandler.removeAwatingMessage(m.getMessageId());
							//Special cases when we get a timeout**************************************
							//*************************************************************************
							
						}
					}
					else {
						try {
							if (mDebug) { System.out.println("[NETWRITE] -  sleep: " + m.getTimeout() + " - " + time + " = " + (m.getTimeout() - time)); }
							Thread.sleep(m.getTimeout() - time);
						} catch (InterruptedException e) {
							if (mDebug) { System.out.println("[NETWRITE] -  Interrupted exception: "+e.getMessage()); }
							break;
						}
					}
				}
			}
		} catch (NullPointerException e){
			if (mDebug) { System.out.println("[NETWRITE] -  Nullpointer in treatWaitingMessages"); }
		}

	}


	/**
	 * It iterates in the outgoing queue sending all of them for priorities
	 */
	private void sendOutgoingMessages() {
		if (mDebug) { System.out.println("[NETWRITE] -  Start of sendoutgoingmessages"); }
		try {
			while (MessageHandler.gotMessagesToSend()) {
				if (mDebug) { System.out.println("[NETWRITE] -  Got messages to send"); }
				//get message with highest priority
				Message m = MessageHandler.getMessageOutgoing();
				if (m == null){
					if (mDebug) { System.out.println("[NETWRITE] -  Message null!!!"); }
				} 
				else 
				{
					synchronized(m) {
						if (mDebug) { System.out.println("[NETWRITE] -  Synchronized m"); }
						//identify the message for special cases: put in queue of awaiting reply if needed
						int port= KayaoValues.CLIENT_READ_PORT;
						
						//send it
						if(m instanceof NotYourKingMessage ) {
							//MessageHandler.insertAwaitingResponseMessage(m);
							
						}
						else if(m instanceof NewMissionMessage ) {
							MessageHandler.insertAwaitingResponseMessage(m);
							
						}
						
						System.out.println("[NETWRITE] -  Sending to: "+m.getTargetAddress() + " a " + m.type()+ " message.\n");
						m.messageSent();
						sendMessage(m, port);
					}
				}
			}
		} catch (NullPointerException e){
			if (mDebug) { System.out.println("[NETWRITE] -  Nullpointer in sendOutgoingMessages"); }
		}
		if (mDebug) { System.out.println("[NETWRITE] -  no messages to send"); }
	}
	
	private void sendMessage(Message message, int port){
		try {			
			packet = new DatagramPacket(message.getData(), message.getData().length, InetAddress.getByName(message.getTargetAddress()), port);
			mSendSocket.send(packet);
			// Save stats of packets
			if (message instanceof LoginMessage || message instanceof TimeMessage){
				MessageHandler.statistics(message.getData()[0], "netwrite2");
			} else {
				MessageHandler.statistics(message.getData()[4], "netwrite");
			}
		} catch (UnknownHostException e) {
			System.out.println("Could not send message: "+e.getMessage());
		} catch (IOException e) {
			System.out.println("Could not send message: "+e.getMessage());
		}
	}

	public void setLogoutCount(int count) {
		this.mLogoutCount = count;
	}
	
	public void decreaseLogoutCounter(){
		this.mLogoutCount--;
	}

}
